﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using LowLevelGraphics;
using LowLevelGraphics.Extractors;

namespace LowLevelGraphics.Extractors
{
    /// <summary>
    /// Extract blobs
    /// </summary>
    public class BlobExtractor : IEnumerable
    {
        private int[] labelBuffer;

        private int[] labelTable;
        private int[] xMinTable;
        private int[] xMaxTable;
        private int[] yMinTable;
        private int[] yMaxTable;
        private int[] massTable;

        private int[,] m_aLabel = null;
        private int[,] m_aLabelTable = null;


        private Blob[] m_aDetectedBlob = null;

        /// <summary>
        /// 
        /// </summary>
        protected UnsafeBitmap m_Bitmap = null;
        protected int m_nWidth = -1;
        protected int m_nHeight = -1;

        /// <summary>
        /// 
        /// </summary>
        protected List<Blob> blobList = new List<Blob>();

        /// <summary>
        /// 
        /// </summary>
        protected Color m_ForegroundColor = Definitions.White;

        /// <summary>
        /// Initializes a new instance of the <see cref="BlobExtractor"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        public BlobExtractor(UnsafeBitmap _bitmap)
        {
            m_Bitmap = _bitmap;

            m_nWidth = m_Bitmap.Width;
            m_nHeight = m_Bitmap.Height;
            m_Bitmap = _bitmap;
            m_aLabel = new int[m_nWidth, m_nHeight];
            m_aLabelTable = new int[m_nWidth, m_nHeight];
            // The maximum number of blobs is given by an image filled with equally spaced single pixel
            // blobs. For images with less blobs, memory will be wasted, but this approach is simpler and
            // probably quicker than dynamically resizing arrays
            int tableSize = m_nWidth * m_nHeight / 4;
            labelTable = new int[tableSize];
        }

        /// <summary>
        /// Detects this instance.
        /// </summary>
        /// <returns></returns>
        public Blob[] Detect()
        {
            int x = 0;
            int y = 0;

            // This is the neighbouring pixel pattern. For position X, A, B, C & D are checked
            // A B C
            // D X

            int width = m_nWidth;
            int height = m_nHeight;

            labelBuffer = new int[width * height];

            int srcPtr = 0;
            int aPtr = -width - 1;
            int bPtr = -width;
            int cPtr = -width + 1;
            int dPtr = -1;

            int label = 1;

            int tableSize = width * height;

            byte[] srcData = new byte[width * height];
            int nCount = 0;
            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    Color colorTemp = m_Bitmap.GetPixel(x, y);
                    srcData[nCount++] = colorTemp.R;
                }
            }
            labelTable = new int[tableSize];
            xMinTable = new int[tableSize];
            xMaxTable = new int[tableSize];
            yMinTable = new int[tableSize];
            yMaxTable = new int[tableSize];
            massTable = new int[tableSize];

            Color color = Color.Transparent;
            int matchVal = 255;
            for (y = 0; y < m_nHeight; y++)
            {
                for (x = 0; x < m_nWidth; x++)
                {
                    color = m_Bitmap.GetPixel(x, y);

                    labelBuffer[srcPtr] = 0;

                    // Check if on foreground pixel
                    if (srcData[srcPtr] == matchVal)
                    {
                        // Find label for neighbours (0 if out of range)
                        int aLabel = (x > 0 && y > 0) ? labelTable[labelBuffer[aPtr]] : 0;
                        int bLabel = (y > 0) ? labelTable[labelBuffer[bPtr]] : 0;
                        int cLabel = (x < width - 1 && y > 0) ? labelTable[labelBuffer[cPtr]] : 0;
                        int dLabel = (x > 0) ? labelTable[labelBuffer[dPtr]] : 0;

                        // Look for label with least value
                        int min = int.MaxValue;
                        if (aLabel != 0 && aLabel < min) min = aLabel;
                        if (bLabel != 0 && bLabel < min) min = bLabel;
                        if (cLabel != 0 && cLabel < min) min = cLabel;
                        if (dLabel != 0 && dLabel < min) min = dLabel;

                        // If no neighbours in foreground
                        if (min == int.MaxValue)
                        {
                            labelBuffer[srcPtr] = label;
                            m_aLabel[x, y] = label;
                            labelTable[label] = label;

                            // Initialise min/max x,y for label
                            yMinTable[label] = y;
                            yMaxTable[label] = y;
                            xMinTable[label] = x;
                            xMaxTable[label] = x;
                            massTable[label] = 1;

                            label++;
                        }

                        // Neighbour found
                        else
                        {
                            // Label pixel with lowest label from neighbours
                            labelBuffer[srcPtr] = min;
                            m_aLabel[x, y] = min;
                            // Update min/max x,y for label
                            yMaxTable[min] = y;
                            massTable[min]++;
                            if (x < xMinTable[min]) xMinTable[min] = x;
                            if (x > xMaxTable[min]) xMaxTable[min] = x;

                            if (aLabel != 0) labelTable[aLabel] = min;
                            if (bLabel != 0) labelTable[bLabel] = min;
                            if (cLabel != 0) labelTable[cLabel] = min;
                            if (dLabel != 0) labelTable[dLabel] = min;
                        }
                    }

                    srcPtr++;
                    aPtr++;
                    bPtr++;
                    cPtr++;
                    dPtr++;

                }
            }

            // Iterate through labels pushing min/max x,y values towards minimum label
            if (blobList == null) blobList = new List<Blob>();

            for (int i = label - 1; i > 0; i--)
            {
                if (labelTable[i] != i)
                {
                    if (xMaxTable[i] > xMaxTable[labelTable[i]]) xMaxTable[labelTable[i]] = xMaxTable[i];
                    if (xMinTable[i] < xMinTable[labelTable[i]]) xMinTable[labelTable[i]] = xMinTable[i];
                    if (yMaxTable[i] > yMaxTable[labelTable[i]]) yMaxTable[labelTable[i]] = yMaxTable[i];
                    if (yMinTable[i] < yMinTable[labelTable[i]]) yMinTable[labelTable[i]] = yMinTable[i];
                    massTable[labelTable[i]] += massTable[i];

                    int l = i;
                    while (l != labelTable[l]) l = labelTable[l];
                    labelTable[i] = l;
                }
                else
                {
                    // Ignore blobs that butt against corners
                    if (i == labelBuffer[0]) continue;									// Top Left
                    if (i == labelBuffer[width]) continue;								// Top Right
                    if (i == labelBuffer[(width * height) - width + 1]) continue;	// Bottom Left
                    if (i == labelBuffer[(width * height) - 1]) continue;			// Bottom Right

                    //if (massTable[i] >= minBlobMass && (massTable[i] <= maxBlobMass || maxBlobMass == -1))
                    {
                        //Blob blob = new Blob(xMinTable[i], xMaxTable[i], yMinTable[i], yMaxTable[i], massTable[i]);
                        //blobList.Add(blob);
                        //}
                    }
                }
            }
            // If dst buffer provided, fill with coloured blobs
            for (int i = label - 1; i > 0; i--)
            {
                if (labelTable[i] != i)
                {
                    int l = i;
                    while (l != labelTable[l]) l = labelTable[l];
                    labelTable[i] = l;
                }
            }

            // Renumber lables into sequential numbers, starting with 0
            int newLabel = 0;
            for (int i = 1; i < label; i++)
            {
                if (labelTable[i] == i)
                {
                    labelTable[i] = newLabel++;
                }
                else
                {
                    labelTable[i] = labelTable[labelTable[i]];
                }
            }

            for (int i = 0; i < label; i++)
            {
                Blob blob = new Blob(i);
                blobList.Add(blob);
            }

            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    int nLabel = m_aLabel[x, y];
                    int c = labelTable[nLabel];
                    blobList[c].Points.Add(new Point(x, y));
                }
            }

            nCount = blobList.Count;
            for (int i = 0; i < nCount; i++)
            {
                Blob blob = blobList[i];
                if (blob.Points.Count == 0)
                {
                    blobList.Remove(blob);
                    nCount--;
                }
            }
            return blobList.ToArray();
        }

        /// <summary>
        /// Gets the minimum.
        /// </summary>
        /// <param name="aLabel">A label.</param>
        /// <returns></returns>
        public int GetMinimum(int[] aLabel)
        {
            Array.Sort(aLabel);
            foreach (int i in aLabel)
            {
                if (i > 0) return i;
            }
            return -1;
        }

        public int[,] Label
        {
            get { return m_aLabel; }
        }

        #region IEnumerable Members

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator GetEnumerator()
        {
            return m_aDetectedBlob.GetEnumerator();
        }

        #endregion
    }
}
